Technical Note TN1198
SndPlayDoubleBuffer and Carbon



目次

削除された API

削除された理由

削除された API のかつての機能

削除された API を置き換える方法

SndStartFilePlay

SndRecordToFileSPBRecordToFile

要約

のテクニカルノートでは、SndPlayDoubleBuffer をはじめとするいくつかの API が Carbon API セットから削除されたことと、これらの消滅した API に基づく既存のサウンドコードを Carbon 互換にするためには何をすればいいかについて説明します。

このテクニカルノートは、Carbon Sound Manager に含まれていない関数を呼び出すコードをすでに開発していて、そのコードを Carbon に移行したいと考えているアプリケーション開発者向けに書かれています。



失われた API


次のサウンド関数は Carbon には含まれていません。

  • SndControl
  • SndStartFilePlay
  • SndPauseFilePlay
  • SndStopFilePlay
  • SndPlayDoubleBuffer
  • MACEVersion
  • Comp3to1
  • Exp1to3
  • Comp6to1
  • Exp1to6
  • AudioGetVolume
  • AudioSetVolume
  • AudioGetMute
  • AudioSetMute
  • AudioSetToDefaults
  • AudioGetInfo
  • AudioGetBass
  • AudioSetBass
  • AudioGetTreble
  • AudioSetTreble
  • AudioGetOutputDevice
  • AudioMuteOnEvent
  • SndRecordToFile
  • SPBRecordToFile

次の SndCommand は Carbon Sound Manager ではサポートされていません。

  • initCmd
  • freeCmd
  • totalLoadCmd
  • loadCmd
  • freqDurationCmd
  • restCmd
  • freqCmd
  • ampCmd
  • timbreCmd
  • getAmpCmd
  • waveTableCmd
  • phaseCmd
  • rateCmd
  • continueCmd
  • doubleBufferCmd
  • getRateCmd
  • sizeCmd /* 廃止されたコマンド */
  • convertCmd /* 廃止された MACE コマンド */

コードが上述の呼び出しまたは SndCommand を拠り所にしている場合、アプリケーションを Carbon 互換にするためにはコードの書き換えが必要になります。このテクニカルノートでは、これらの API の機能を Carbon 互換コードを使って再現する方法について説明します。


削除された理由


ローレベル呼び出しは、互換性を維持しつつ、それらの機能をより効率的に実現する方法が存在するため削除されました。場合によっては、呼び出しそのものがすでに役割を終えているため、それを削除してもモダンコードベースに特に支障がないこともあります。

SndStartFilePlay、SPBRecordToFile などのハイレベル呼び出しは、それらの機能が大部分 QuickTime に包括されているため削除されました。これらのルーチンの代わりに QuickTime を使用すると、既存のコードベースに追加の作業やコードを加えることなく、プログラムの機能を向上させることができます (たとえば、よりバリエーションに富んだサウンドファイルのリストを再生できます)。

上述の SndCommand は、それらがもはやサポートされていないノンウェーブデータを対象に動作したり、それらの機能に取って代わるより新しい SndCommand が存在するため削除されました。


削除された API のかつての機能


次に、それぞれの関数がかつて実行していた機能の概要を示します。詳細については、『Inside Macintosh: Sound』を参照してください。

  • SndControl
    • Sound Manager 2.0 で、サウンドハードウェアに関する情報を返していました。
  • SndStartFilePlay
    • ディスクからファイルの再生を開始します。この関数は、基本的に SndPlayDoubleBuffer を包むラッパーです。
  • SndPauseFilePlay
    • SndStartFilePlay によって再生されるファイルの再生状態を切り替えます。
  • SndStopFilePlay
    • SndStartFilePlay によって再生されるサウンドを停止します。
  • SndPlayDoubleBuffer
    • サウンドを再生するときに、まずオーディオの一方のバッファを再生した後で、さらにもう一方のバッファを再生することで、一方のバッファを再生している間にもう一方のバッファにディスク (またはディスク以外の場所) からデータをロードすることを可能にします。この API により、最小限の RAM を使用するだけで、サイズの大きなサウンドファイルを再生できるようになります。
  • MACEVersion
    • MACE 圧縮プログラム/伸張プログラムのバージョンを取得します。
  • Comp3to1
    • MACE 3:1 でサウンドを圧縮します。
  • Exp1to3
    • MACE 3:1 で圧縮されたサウンドを伸張します。
  • Comp6to1
    • MACE 6:1 でサウンドを圧縮します。
  • Exp1to6
    • MACE 6:1 で圧縮されたサウンドを伸張します。
  • AudioGetVolume
    • Sound Manager によって呼び出され、出力コンポーネントのボリュームを取得します。
  • AudioSetVolume
    • Sound Manager によって呼び出され、出力コンポーネントのボリュームを設定します。
  • AudioGetMute
    • Sound Manager によって呼び出され、出力コンポーネントの消音状態を取得します。
  • AudioSetMute
    • Sound Manager によって呼び出され、出力コンポーネントの消音状態を設定します。
  • AudioSetToDefaults
    • Sound Manager によって呼び出され、出力コンポーネントをそのデフォルトに戻します。
  • AudioGetInfo
    • Sound Manager によって呼び出され、出力コンポーネントに関する情報を取得します。
  • AudioGetBass
    • Sound Manager によって呼び出され、出力コンポーネントの低音ボリュームを取得します。
  • AudioSetBass
    • Sound Manager によって呼び出され、出力コンポーネントの低音ボリュームを設定します。
  • AudioGetTreble
    • Sound Manager によって呼び出され、出力コンポーネントの高音ボリュームを取得します。
  • AudioSetTreble
    • Sound Manager によって呼び出され、出力コンポーネントの高音ボリュームを設定します。
  • AudioGetOutputDevice
    • マニュアル化されていません。
  • AudioMuteOnEvent
    • マニュアル化されていません。
  • SndRecordToFile
    • 同期操作によりサウンドデータをファイルに記録します。
  • SPBRecordToFile
    • Rオプションの設定により、非同期的にサウンドデータをファイルに記録します。

  • initCmd
    • マニュアル化されていません。
  • freeCmd
    • マニュアル化されていません。
  • totalLoadCmd
    • 廃止された SndControl 関数を使って送信され、既存のサウンド処理と param2 で指定される初期化パラメータを持つ新規サウンドチャネルによる CPU 負荷の総計をレポートしていました。
  • loadCmd
    • 廃止された SndControl 関数を使って送信され、param2 で指定されるサウンドチャネルが必要とする CPU 処理パワーのパーセンテージをレポートしていました。
  • freqDurationCmd
    • param2 で指定される音符を param1 で指定される時間にわたって再生します。
  • freqCmd
    • サウンドの振動数 (またはピッチ) を変更します。現在サウンドが再生されていないと、freqCmd は、Sound Manager に param2 で指定される振動数で無期限に再生を開始させます。ある soundCmd によってインストールされたサンプリングサウンドのデータをループ再生するときに使用できました。
  • restCmd
    • 指定した時間にわたってチャネルを休止します。
  • ampCmd
    • サウンドの振幅 (大きさ) を変更します。
  • timbreCmd
    • 現在方形波データを使って定義されているサウンドの音質 (トーン) を変更します。
  • getAmpCmd
    • サウンドの現在の振幅 (大きさ) を取得します。
  • waveTableCmd
    • 指定されたチャネルのボイスとしてウェーブテーブルをインストールします。
  • phaseCmd
    • マニュアル化されていません。
  • rateCmd
    • 現在再生中のサンプリングサウンドのレートを設定し、そのピッチと継続時間を効率的に変更します。アプリケーションはレートを 0 に設定して、再生中のサンプリングサウンドを休止できます。新しいレートは、22 kHz が基準と解釈される param2 で指定される値に設定されます。
  • continueCmd
    • マニュアル化されていません。
  • doubleBufferCmd
    • これは、SndPlayDoubleBuffer によって使用されるコマンドです。
  • getRateCmd
    • 現在再生中のサンプリングサウンドのサンプリングレートを取得します。チャネルの現在のレートは、サウンドコマンドの param2 に渡すアドレスを持った Fixed 変数によって返されます。返される値は、rateCmd サウンドコマンドと同様に、常に 22 kHz のサンプリングレートを基準にしています。
  • sizeCmd /*obsolete command*/
    • マニュアル化されていません。
  • convertCmd /*obsolete MACE command*/
    • マニュアル化されていません。

削除された API を置き換える方法

  • SndControl
    • この呼び出しは Sound Manager 3.0 で廃止されました。Gestalt 呼び出しで置き換えてください。
  • SndStartFilePlay
    • QuickTime でシミュレートすることができます。
  • SndPauseFilePlay
    • QuickTime を使用するときは、QuickTime の制御コマンドを使用します。
  • SndStopFilePlay
    • QuickTime を使用するときは、QuickTime の制御コマンドを使用します。
  • SndPlayDoubleBuffer
    • bufferCmd と callBackCmd でシミュレートするか、ある程度は QuickTime でもシミュレートできます。
  • MACEVersion
    • この呼び出しは今後絶対に必要ありません。MACE の現在のバージョンは 1.0.2 で、ここ何年にもわたって変更されていません。
  • Comp3to1
    • SoundConverter API の適切な呼び出しによって置き換えることができます。
  • Exp1to3
    • SoundConverter API の適切な呼び出しによって置き換えることができます。
  • Comp6to1
    • SoundConverter API の適切な呼び出しによって置き換えることができます。
  • Exp1to6
    • SoundConverter API の適切な呼び出しによって置き換えることができます。
  • AudioGetVolume
    • SoundComponentGetInfo と siHardwareVolume セレクタで置き換えることができます。
  • AudioSetVolume
    • SoundComponentSetInfo と siHardwareVolume セレクタで置き換えることができます。
  • AudioGetMute
    • SoundComponentGetInfo と siHardwareMute セレクタで置き換えることができます。
  • AudioSetMute
    • SoundComponentSetInfo と siHardwareMute セレクタで置き換えることができます。
  • AudioSetToDefaults
    • 置き換える方法はありません。
  • AudioGetInfo
    • SoundComponentGetInfo 呼び出しと適切なセレクタで置き換えることができます。
  • AudioGetBass
    • SoundComponentGetInfo と siHardwareBass セレクタで置き換えることができます。
  • AudioSetBass
    • SoundComponentSetInfo と siHardwareBass セレクタで置き換えることができます。
  • AudioGetTreble
    • SoundComponentGetInfo と siHardwareTreble セレクタで置き換えることができます。
  • AudioSetTreble
    • SoundComponentSetInfo と siHardwareTreble セレクタで置き換えることができます。
  • AudioGetOutputDevice
    • 置き換える方法はありません。
  • AudioMuteOnEvent
    • 置き換える方法はありません。
  • SndRecordToFile
    • Sequence Grabber (QuickTime) でシミュレートできます。
  • SPBRecordToFile
    • 非同期の場合は、SPBRecord と PBWriteAsync の適切な呼び出しでシミュレートできます。また、QuickTime を使って置き換えることもできます。

  • initCmd
    • 置き換える方法はありません。
  • freeCmd
    • 置き換える方法はありません。
  • totalLoadCmd
    • 置き換える方法はありませんが、Sound Manager 3.1 以降、このコマンドの正確さと有用性は失われました。
  • loadCmd
    • 置き換える方法はありませんが、Sound Manager 3.1 以降、このコマンドの正確さと有用性は失われました。
  • freqDurationCmd
    • ピッチシフト機能は rateMultiplierCmd で置き換えます。
  • freqCmd
    • ピッチシフト機能と継続時間機能を rateMultiplierCmd で置き換えます。もはやサウンドをループ再生することはできません。サウンドの再生をループさせるには、bufferCmd と callBackCmd を繰り返し使用します。
  • restCmd
    • 方形波データサウンドおよびウェーブテーブルデータサウンドと置き換える方法は、Carbon Sound Manager ではサポートされていません。
  • ampCmd
    • 代わりに volumeCmd を使用します。
  • timbreCmd
    • 方形波データサウンドおよびウェーブテーブルデータサウンドと置き換える方法は、Carbon Sound Manager ではサポートされていません。
  • getAmpCmd
  • waveTableCmd
    • ウェーブテーブルデータサウンドと置き換える方法は、Carbon Sound Manager ではサポートされていません。
  • phaseCmd
    • 置き換える方法はありません。
  • rateCmd
    • 代わりに rateMultiplierCmd を使用します。
  • continueCmd
    • 置き換える方法はありません。
  • doubleBufferCmd
    • これは、SndPlayDoubleBuffer によって使用されるコマンドです。直接的に置き換える方法はありません。ただし、bufferCmd と callBackCmd を使ってシミュレートすることはできます。詳細については、後述の「SndPlayDoubleBuffer の置き換え」を参照してください。
  • getRateCmd
    • 代わりに getRateMultiplierCmd を使用します。
  • sizeCmd /* 廃止されたコマンド */
    • 置き換える方法はありません。
  • convertCmd /* 廃止された MACE コマンド */
    • 置き換える方法はありません。

SndPlayDoubleBuffer の置き換え

SndPlayDoubleBuffer の動作が本来微妙なものであったため、SndPlayDoubleBuffer の置き換えは若干トリッキーなものになります。SndPlayDoubleBuffer を置き換えても、bufferCmd と callBackCmd の単純なストリームは、本物の SndPlayDoubleBuffer を期待するコードと厳密に同じ動作を行いません。

これは、本物の SndPlayDoubleBuffer 呼び出しがサウンドチャネルのキュー内の単一のコマンド (doubleBufferCmd) であるのに対して、代替方法は 2 つのコマンドであるためです。さらに、doubleBufferCmd はサウンドの再生が完了するまでチャネルのキュー内に留まりますが、bufferCmd と callBackCmd は常時追加または削除されます。このことは、キュー内で doubleBufferCmd の後に格納されているすべてのコマンドと、doubleBufferCmd の後に格納されているコマンドに基づくコードにとっては大きな問題になります。このことは、SndPlayDoubleBuffer のシミュレートの見通しを即座に否定するものではありませんが、コードのシミュレートに必要な作業がかなり複雑なものになることは否定できません。

第 1 の問題は、現在再生中のサウンドに関する情報が各サウンドチャネルに常時保持されている必要があるということです。この情報は、本物の SndPlayDoubleBuffer を使用するときに Sound Manager によって保持されます。


// 構造体
struct PerChanInfo {
    QElemPtr             qLink;    /* 次のキューエントリ */
    short                qType;    /* キュータイプ = 0 */
    short                stopping;
    #if DEBUG
        OSType           magic;
    #endif
    SndCallBackUPP        usersCallBack;
    SndDoubleBufferHeader   theParams;
    CmpSoundHeader        soundHeader;
};
typedef struct PerChanInfo   PerChanInfo;
typedef struct PerChanInfo * PerChanInfoPtr;
                  
// グローバル
    Boolean              gNMRecBusy;
    NMRecPtr             gNMRecPtr;
    QHdrPtr              gFreeList;
    Ptr                 gSilenceTwos;
    Ptr                 gSilenceOnes;

キュー構造体は、サウンドの形式、SndPlayDoubleBuffer に渡すパラメータ、もともとサウンドチャネル内にあったコールバック関数 (コードをシミュレートする前にユーザが独自の目的で使用していた)、その他いくつかの保守情報などの、チャネルごとのサウンド情報を追跡するために使用されます。このキュー構造体により、割り込み時に PBEnqueue を使用してチャネル情報をキューに格納できるようになるため、タスク実行時にはチャネルごとの構造体と関連するメモリを破棄することができます。

シミュレーションはその単純な状態のマシンをセットアップする必要があり、さらにサウンドの第 1 バッファを再生する必要があります。そのコードは以下のようになります。


// これはタスク実行時に呼び出される必要がある
OSErr    CarbonSndPlayDoubleBuffer (SndChannelPtr chan, 
  SndDoubleBufferHeaderPtr theParams) {
    OSErr                            err;
    CompressionInfo                    compInfo;
    PerChanInfoPtr                    perChanInfoPtr;
    SndCommand                        playCmd;
    SndCommand                        callBack;
                  
    if (nil == chan) {
        err = badChannel;
        goto exit;
    }
                  
    if (nil == theParams) {
        err = paramErr;
        goto exit;
    }
                  
    if (nil == gFreeList) {
        // いつ使用しなければならなくなるか (割り込み時に)
        // 予想できないため、これは絶対に破棄できない
        gFreeList = (QHdrPtr)NewPtrClear (sizeof (QHdr));
        err = MemError ();
        if (noErr != err) goto exit;
    }
                  
    if (nil == gSilenceOnes) {
        short        i;
        // いつ使用しなければならなくなるか (割り込み時に)
        // 予想できないため、これは絶対に破棄できない
        gSilenceOnes = NewPtr (kBufSize);
        err = MemError ();
        if (noErr != err) goto exit;
        for (i = 0; i < kBufSize; i++) {
            gSilenceOnes[i] = (char)0x80;
        }
    }
                  
    if (nil == gSilenceTwos) {
        // いつ使用しなければならなくなるか (割り込み時に)
        // 予想できないため、これは絶対に破棄できない
        gSilenceTwos = NewPtrClear (kBufSize);
        err = MemError ();
        if (noErr != err) goto exit;
    }
                  
    if (nil == gNMRecPtr) {
        // いつ使用しなければならなくなるか (割り込み時に)
        // 予想できないため、これは絶対に破棄できない
        gNMRecPtr = (NMRecPtr)NewPtr (sizeof (NMRec));
        err = MemError ();
        if (noErr != err) goto exit;
                  
        // メモリの大部分 (すべてではなく) を破棄することになる
        // NMProc 情報をセットアップする
        gNMRecPtr->qLink = nil;
        gNMRecPtr->qType = 8;
        gNMRecPtr->nmFlags = 0;
        gNMRecPtr->nmPrivate = 0;
        gNMRecPtr->nmReserved = 0;
        gNMRecPtr->nmMark = nil;
        gNMRecPtr->nmIcon = nil;
        gNMRecPtr->nmSound = nil;
        gNMRecPtr->nmStr = nil;
        gNMRecPtr->nmResp = NewNMProc (NMResponseProc);
        gNMRecPtr->nmRefCon = 0;
    }
                  
    perChanInfoPtr = (PerChanInfoPtr)NewPtr (sizeof (PerChanInfo));
    err = MemError ();
    if (noErr != err) goto exit;
                  
    // チャネル情報ごとの基本的な初期値
    perChanInfoPtr->qLink = nil;
    perChanInfoPtr->qType = 0;                // 使用されない
    perChanInfoPtr->stopping = 0;
    #if DEBUG
        perChanInfoPtr->magic = 'SANE';
    #endif
    
    perChanInfoPtr->theParams = *theParams;
    // 独自のコールバック関数で上書きしようとしているため、
    // サウンドに含まれるユーザのコールバック関数を覚えておく必要がある
    perChanInfoPtr->usersCallBack = chan->callBack;
                  
    // SndPlayDoubleBuffer 呼び出しによって渡されるバッファの再生に使用する
    // bufferCmd のサウンドヘッダをセットアップする
    perChanInfoPtr->soundHeader.samplePtr = 
         (Ptr)(theParams->dbhBufferPtr[0]->dbSoundData);
    perChanInfoPtr->soundHeader.numChannels = 
         theParams->dbhNumChannels;
    perChanInfoPtr->soundHeader.sampleRate = 
         theParams->dbhSampleRate;
    perChanInfoPtr->soundHeader.loopStart = 0;
    perChanInfoPtr->soundHeader.loopEnd = 0;
    perChanInfoPtr->soundHeader.encode = cmpSH;
    perChanInfoPtr->soundHeader.baseFrequency = kMiddleC;
    perChanInfoPtr->soundHeader.numFrames = 
         (unsigned long)theParams->dbhBufferPtr[0]->dbNumFrames;
    //    perChanInfoPtr->soundHeader.AIFFSampleRate = 0;          // 使用されない
    perChanInfoPtr->soundHeader.markerChunk = nil;
    perChanInfoPtr->soundHeader.futureUse2 = nil;
    perChanInfoPtr->soundHeader.stateVars = nil;
    perChanInfoPtr->soundHeader.leftOverSamples = nil;
    perChanInfoPtr->soundHeader.compressionID = 
         theParams->dbhCompressionID;
    perChanInfoPtr->soundHeader.packetSize = 
         (unsigned short)theParams->dbhPacketSize;
    perChanInfoPtr->soundHeader.snthID = 0;
    perChanInfoPtr->soundHeader.sampleSize = 
         (unsigned short)theParams->dbhSampleSize;
    perChanInfoPtr->soundHeader.sampleArea[0] = 0;
                  
    // サウンドは圧縮されているか? もしそうなら、
    // theParams を SndDoubleBufferHeader2Ptr として取り扱う必要がある
    if (0 != theParams->dbhCompressionID) {
        // サウンドは圧縮されている
        err = GetCompressionInfo (theParams->dbhCompressionID,
               ((SndDoubleBufferHeader2Ptr)theParams)->dbhFormat,
               theParams->dbhNumChannels,
               theParams->dbhSampleSize,
               &compInfo);
        if (noErr != err) goto exitDispose;
                  
        perChanInfoPtr->soundHeader.format = compInfo.format;
    } else {
        // サウンドは圧縮されていない
        perChanInfoPtr->soundHeader.format = kSoundNotCompressed;
    }
                  
    playCmd.cmd = bufferCmd;
    playCmd.param1 = 0;          // 使用されない
    playCmd.param2 = (long)&perChanInfoPtr->soundHeader;
                  
    callBack.cmd = callBackCmd;
    callBack.param1 = 0;          // いっぱいにするバッファ、0 バッファ、1、0、...
    callBack.param2 = (long)perChanInfoPtr;
                  
    // コールバック関数のポインタをサウンドチャネル構造体の
    // 内部に直接インストールする
    if (nil == gCarbonSndPlayDoubleBufferCallBackUPP) {
        gCarbonSndPlayDoubleBufferCallBackUPP = 
             NewSndCallBackProc (CarbonSndPlayDoubleBufferCallBackProc);
    }
                  
    chan->callBack = gCarbonSndPlayDoubleBufferCallBackUPP;
                  
    if (nil == gCarbonSndPlayDoubleBufferCleanUpUPP) {
        #if !TARGET_API_MAC_CARBON
            gCarbonSndPlayDoubleBufferCleanUpUPP = 
                 NewSndCallBackProc (CarbonSndPlayDoubleBufferCleanUpProc);
        #endif
    }
                  
    err = SndDoCommand (chan, &playCmd, true);
    if (noErr != err) goto exitDispose;
                  
    err = SndDoCommand (chan, &callBack, true);
    if (noErr != err) goto exitDispose;
                  
exit:
    return err;
                  
exitDispose:
    if (nil != perChanInfoPtr)
        DisposePtr ((Ptr)perChanInfoPtr);
    goto exit;
}

Carbon には、SndDoubleBackProc に対する UPP は存在しませんが、特に問題はありません。Carbon のすべてのコードは PowerPC コードであり、このコードは呼び出しプログラムとしてコンパイルされるため (したがって、CFM<->Mach-O 呼び出し規約を考慮する必要はありません)、SndDoubleBackProc は単純に正規の C の関数ポインタとして取り扱われます。

ユーザのコードに現在空のバッファをいっぱいにすることを指示し、代替バッファの再生を開始するコールバック関数は以下のようになります。


static pascal void    CarbonSndPlayDoubleBufferCallBackProc 
               (SndChannelPtr theChannel, SndCommand * theCallBackCmd) {
    SndDoubleBufferHeaderPtr        theParams;
    SndDoubleBufferPtr                emptyBuf;
    SndDoubleBufferPtr                nextBuf;
    PerChanInfoPtr                    perChanInfoPtr;
    SndCommand                        playCmd;
                  
    perChanInfoPtr = (PerChanInfoPtr)(theCallBackCmd->param2);
    #if DEBUG
        if (perChanInfoPtr->magic != 'SANE') 
                    DebugStr("¥pBAD in CarbonSndPlayDoubleBufferCallBackProc");
    #endif
    if (true == perChanInfoPtr->stopping) goto exit;
                  
    theParams = &(perChanInfoPtr->theParams);
                  
    // 再生して、いっぱいにする必要のあるバッファ
    emptyBuf = theParams->dbhBufferPtr[theCallBackCmd->param1];
                  
    // ready フラグをクリアする
    emptyBuf->dbFlags ^= dbBufferReady;
                  
    // これは、いま再生するバッファ
    nextBuf = theParams->dbhBufferPtr[!theCallBackCmd->param1];
                  
    // 準備ができているかどうか、またはしばらく待機する必要があるかどうかをチェックする
     if (nextBuf->dbFlags & dbBufferReady) {
  perChanInfoPtr->soundHeader.numFrames = 
     (unsigned long)nextBuf->dbNumFrames;
          perChanInfoPtr->soundHeader.samplePtr = Ptr)(nextBuf->dbSoundData);
                  
// ビットを反転して、次のバッファを指示する
theCallBackCmd->param1 = !theCallBackCmd->param1;
                  
// これが最後のバッファでない場合は、ユーザのフィルルーチンを呼び出す
     if (!(nextBuf->dbFlags & dbLastBuffer)) {
          #if TARGET_API_MAC_CARBON
          // ユーザの double back proc への関数ポインタを宣言する
          void (*doubleBackProc)(SndChannel*, SndDoubleBuffer*);
                  
               // ユーザの double back proc を呼び出す
              doubleBackProc = (void*)theParams->dbhDoubleBack;
              (*doubleBackProc) (theChannel, emptyBuf);
            #else
              CallSndDoubleBackProc (theParams->dbhDoubleBack, theChannel, emptyBuf);
            #endif
        } else {
            // 最後のバッファが終了したときに clean up proc を呼び出す
            theChannel->callBack = gCarbonSndPlayDoubleBufferCleanUpUPP;
        }
    } else {
        // バッファの準備ができるまで待機する必要がある。
        // 本物の SndPlayDoubleBuffer はごく短いサイレントを再生して、
        // ユーザがディスクからオーディオを読み込むのを待つため、
        // ここでも同じことを行う
        #if DEBUG
            DebugStr ("¥p buffer is not ready!");
        #endif
        // 短いサイレントを再生するため、
        // 再度 ready フラグをチェックできる
        if (theParams->dbhSampleSize == 8) {
            perChanInfoPtr->soundHeader.numFrames = 
                   (UInt32)(kBufSize / theParams->dbhNumChannels);
            perChanInfoPtr->soundHeader.samplePtr = gSilenceOnes;
        } else {
            perChanInfoPtr->soundHeader.numFrames = 
                   (UInt32)(kBufSize / (theParams->dbhNumChannels * 
                   (theParams->dbhSampleSize / 8)));
            perChanInfoPtr->soundHeader.samplePtr = gSilenceTwos;
        }
    }
                  
    // コールバックコマンドを挿入する
    InsertSndDoCommand (theChannel, theCallBackCmd);
                  
    // 次のバッファを再生する
    playCmd.cmd = bufferCmd;
    playCmd.param1 = 0;
    playCmd.param2 = (long)&(perChanInfoPtr->soundHeader);
    InsertSndDoCommand (theChannel, &playCmd);
                  
exit:
    return;
}

データの最後のバッファを再生したというシグナルをアプリケーションが発したときに一度だけ実行される、さらにもう 1 つのコールバック関数が必要です。この関数は、チャネルごとのサウンド情報をキューに格納するため、Notification Manager コールバックはチャネルごとのメモリを破棄することができます。


static pascal void    CarbonSndPlayDoubleBufferCleanUpProc 
              (SndChannelPtr theChannel, SndCommand * theCallBackCmd) {
    PerChanInfoPtr perChanInfoPtr;
                  
    perChanInfoPtr = (PerChanInfoPtr)(theCallBackCmd->param2);
    #if DEBUG
        if (perChanInfoPtr->magic != 'SANE') DebugStr("¥pBAD in 
             CarbonSndPlayDoubleBufferCleanUpProc");
    #endif
                  
    // チャネルごとのデータを空いているキューに格納するため
    // 後でクリーンアップできる
    Enqueue ((QElemPtr)perChanInfoPtr, gFreeList);
    // gFreeList をクリーンアップできるように
    // Notification Manager ルーチンをインストールする必要がある
    if (! OTAtomicSetBit (&gNMRecBusy, 0)) {
        NMInstall (gNMRecPtr);
    }
    // 次のバッファが終了したときに呼び出されるように
    // ユーザのコールバックプロシージャを戻す
    theChannel->callBack = perChanInfoPtr->usersCallBack;
}

さらに、サウンドが終了した後でコードのシミュレーションが呼び出されることはないため、完了したサウンドに関連するメモリを破棄する必要があるときに、シミュレーションそのものの (割り込み時に呼び出される) 後でメモリのクリーンアップを行うチャンスがなくなるという問題があります。この問題は、Notification Manager を使用して大部分のメモリを破棄することで部分的に緩和できる可能性がありますが、このために割り当てられた Notification Manager レコードを簡単にクリーンアップする方法はありません。

Notification Manager コールバックのコードは以下のようになります。


static pascal void NMResponseProc (NMRecPtr nmReqPtr) {
    PerChanInfoPtr                    perChanInfoPtr;
    OSErr                            err;
                  
    NMRemove (nmReqPtr);
    gNMRecBusy = false;
                  
    do {
        perChanInfoPtr = (PerChanInfoPtr)gFreeList->qHead;
        if (nil != perChanInfoPtr) {
            err = Dequeue ((QElemPtr)perChanInfoPtr, gFreeList);
            if (noErr == err) {
                DisposePtr ((Ptr)perChanInfoPtr);
            }
        }
    } while (nil != perChanInfoPtr && noErr == err);
}

既存のコードでは、SndPlayDoubleBuffer を呼び出した後で、サウンドの再生に影響を与えることなく callBackCmd をインストールできることを前提としているため、SndPlayDoubleBuffer を置き換えるコードでは、その bufferCmd と callBackCmd をサウンドキューの先頭に挿入する必要があります。このことは、サウンドチャネルのコマンドキューを直接的に取り扱うことを意味します。

このためのコードは以下のようになります。


static void    InsertSndDoCommand (SndChannelPtr chan, SndCommand * newCmd) {
    if (-1 == chan->qHead) {
        chan->qHead = chan->qTail;
    }
                  
    if (1 <= chan->qHead) {
        chan->qHead--;
    } else {
        chan->qHead = chan->qTail;
    }
                  
    chan->queue[chan->qHead] = *newCmd;
}

このことはさらに、オリジナルのコードが quietCmd を使うことができるように、SndDoImmediate をラップしなければらないことを意味しています。SndPlayDoubleBuffer を呼び出した後に、quietCmd がサウンドを停止して、インストールされていた何らかの callBackCmd に対する正しい関数を呼び出すことを確認するために、どのように SndDoImmediate をラップするのかを示すコードです。


// このルーチンは割り込み時に呼び出すことができるため、
// メモリの割り当てや解除は行わない
OSErr    MySndDoImmediate (SndChannelPtr chan, SndCommand * cmd) {
    PerChanInfoPtr                    perChanInfoPtr;
                  
    // これは、対象となるサウンドチャネルのいずれかで呼び出されているか?
    // もしそうなら、ユーザのコマンドが実行できるように、
    // われわれのコールバックを抜き取る必要がある
    if (nil != gFreeList && gCarbonSndPlayDoubleBufferCallBackUPP == 
                        chan->callBack) {
        if (quietCmd == cmd->cmd || flushCmd == cmd->cmd) {
            // これがわれわれのチャネルであれば、
            // callBackCmd がキューの先頭項目であることは明らか
            perChanInfoPtr = (PerChanInfoPtr)
                      (chan->queue[chan->qHead].param2);
            #if DEBUG
                if (perChanInfoPtr->magic != 'SANE') 
                          DebugStr("¥pBAD in MySndDoImmediate");
            #endif
            perChanInfoPtr->stopping = true;
            Enqueue ((QElemPtr)perChanInfoPtr, gFreeList);
            if (! OTAtomicSetBit (&gNMRecBusy, 0)) {
                NMInstall (gNMRecPtr);
            }
            chan->callBack = perChanInfoPtr->usersCallBack;
        }
    }
                  
    return (SndDoImmediate (chan, cmd));
}


SndStartFilePlay

SndStartFilePlay を使用して、制限された容量のメモリでサウンドリソース (タイプが 'snd' のリソース) を再生している場合、唯一のソリューションは、ReadPartialResource を使って CarbonSndPlayDoubleBuffer のコードを包むラッパーを書き、一度にサウンドリソースの一部のみを抽出することです。

QuickTime には、完全にロードされていないデータを含むリソースハンドルを再生するオプションは用意されていません。QuickTime もサウンドリソースを再生しますが、そのリソースがメモリ内に完全にロードされていることが前提になります。

一方、SndStartFilePlay を使用してディスクからサウンドファイルを再生している場合は、QuickTime を使用するのがおそらく最適です。SndStartFilePlay とまったく同様に、特定の時点からサウンドの再生を開始するように QuickTime に指示することはできますが、サウンドがいつ終了するかを知るためにポーリングを行う必要があり、QuickTime にはこの情報を取得するためのコールバックは存在しません。

QuickTime にディスクからファイルを再生させるコードは以下のようになります。

    OSErr            err;
    Movie            theSound;
    short            fileRefNum;
                  
    err = OpenMovieFile (&theSpec, &fileRefNum, fsReadPerm);
                  
    if (noErr == err) {
          err = NewMovieFromFile (&theSound, fileRefNum, 0, nil, 
             newMovieActive, nil);
    }
                  
    if (noErr == err) {
        GoToBeginningOfMovie (theSound);
    }
                  
    if (noErr == err) {
        StartMovie (theSound);
    }
                  
    if (noErr == err) {
        while (!IsMovieDone (theSound) {
            MoviesTask (theSound, 0);
        }
    }

このコードは、FSSpec によってポイントされたディスク上のファイルをオープンし、それをサウンドの先頭から末尾に向かって同期的に再生します。SndStartFilePlay では、ファイルがすでにオープンされていて、それにファイル参照番号を渡す必要があるため、再生する必要のあるファイルへの FSSpec をあらかじめ用意しておく必要があります。

先頭以外の任意の場所からサウンドの再生を開始する場合は、QuickTime の SetMovieTime 関数を使って、ムービー内の現在の時刻を設定します。

サウンドを非同期的に再生する場合は、theSound をグローバルにし、メインイベントループの中でほぼ 1/4 秒ごとに MoviesTask を呼び出して、任意のにせ信号を使ってムービーの実行を続ける必要があります。このことは、ユーザがマウスボタンを押し続けていたり、TrackDrag を呼び出す必要があるために MoviesTask を呼び出すことができないと、サウンドの再生が停止することを暗示しています。これを避けたい場合は、タスク時間を必要としない CarbonSndPlayDoubleBuffer のコードを使ってサウンドの再生を継続するようにコードを変換する必要があります。


SndRecordToFile and SPBRecordToFile

SndRecordToFile を使ってサウンドをディスクに録音している場合、最も簡単に Carbon への移行を行うには QuickTime を使用します。次のコードは、QuickTime の Sequence Grabber を使って、オーディオを同期的に記録する方法を示しています。


    SGChannel               sgSoundChan;
    ComponentInstance        sgSoundComp;
    short                  numChannels,
                          sampleSize;
    OSType                 compressionType,
                          inputSource;
                  
    err = SGInitialize (sgSoundComp);
                  
    if (err == noErr) {
        err = SGNewChannel (sgSoundComp, SoundMediaType, &sgSoundChan);
    }
                  
    if (err == noErr) {
        err = SGSetChannelUsage (sgSoundChan, seqGrabRecord);
    }
                  
    if (err == noErr) {
        err = SGSetSoundInputRate (sgSoundChan, sampleRate);
    }
                  
    if (err == noErr) {
        err = SGSetSoundInputParameters 
                  (sgSoundChan, sampleSize, numChannels, compressionType);
    }
                  
    if (err == noErr) {
        err = SPBSetDeviceInfo 
                  (SGGetSoundInputDriver (sgSoundChan), 
                  siOSTypeInputSource, &inputSource);
    }
                  
    if (err == noErr) {
        err = SGSoundInputDriverChanged (sgSoundChan);
    }
                  
    if (err == noErr) {
        err = SGSetDataOutput (sgSoundComp, &theSpec, seqGrabToDisk);
    }
                  
    if (err == noErr) {
        err = SGStartRecord (sgSoundComp);
    }
                  
    if (err == noErr) {
        EventRecord        event;
        Boolean            done = false;
                  
        while (!done && err == noErr) {
            WaitNextEvent (mDownMask | keyDownMask, &event, 6, nil);
            err = SGIdle (sgSoundComp);
            switch (event.what) {
                case mouseDown:
                case keyDown:
                    done = true;
                    break;
            }
        }
        err = SGStop (sgSoundComp);
    }
                  
    if (sgSoundComp != nil) {
        err = CloseComponent (sgSoundComp);
    }

Sequence Grabber のユーザインタフェースを使用して、ユーザに録音の設定をさせたい場合は (すでに独自のインタフェースを用意していない場合はこの方法をお勧めします)、siOSTypeInputSource セレクタを含む SGSetSoundInputRate、SGSetSoundInputParameters、SPBSetDeviceInfo、および SGSoundInputDriverChanged の呼び出しをスキップして、これらすべてを単一の SGSettingsDialog 呼び出しに置き換えることができます。

非同期的に録音を行う必要がある場合は (SndRecordToFile は非同期録音を行いませんが、SPBRecordToFile は行います)、sgSoundComp をグローバルにして、メインイベントループから少なくとも 1/4 秒ごとに SGIdle を呼び出す必要があります。

Sequence Grabber を使用して録音を行ったときに提供される優れた機能の 1 つは、現在 Mac にインストールされている任意の圧縮形式で録音ができるということです。したがって、選択肢は MACE 3:1 および MACE 6:1 圧縮だけに制限されません。


要約

Carbon に含まれていない Sound Manager 関数の多くはすでにその使命を終えているものであるため、それらが削除されてもさしたる問題は発生しません。その他一部の関数に対しては、QuickTime と少数の特別な Sound Manager Carbon 互換コードが必要な処理すべてを肩代わります。

QuickTime は使用が非常に容易であり (少なくとも対象となるケースでは)、非 Carbon Sound Manager 呼び出しの大部分の機能を備えているため、コードの他の部分を Carbon に変換することがそれほど困難でないときは、QuickTime を使用するようにコード全体を変換してください。

SndPlayDoubleBuffer を大々的に使用し、中断されないオーディオを配信するためにその割り込み駆動型の特質を必要とするアプリケーションの場合は、CarbonSndPlayDoubleBuffer とそれに関連するいくつかの関数により、既存のコードベースに加える変更を最小限に抑えつつ同様の機能を実現することができます。


参考文献


ファイルのダウンロード

bluebook CarbonSndPlayDoubleBuffer のコード



更新日: 2000 年 6 月 19 日